/*-------------------------------------------------------------------------
 *
 * ginscan.c
 *	  管理倒排索引关系扫描的例程
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *			src/backend/access/gin/ginscan.c
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/gin_private.h"
#include "access/relscan.h"
#include "pgstat.h"
#include "utils/memutils.h"
#include "utils/rel.h"


IndexScanDesc ginbeginscan(Relation fc_rel, int fc_nkeys, int fc_norderbys)
{
	IndexScanDesc fc_scan;
	GinScanOpaque fc_so;

	/* 不允许使用 order by 操作符 */
	Assert(fc_norderbys == 0);

	fc_scan = RelationGetIndexScan(fc_rel, fc_nkeys, fc_norderbys);

	/* 分配私有工作空间 */
	fc_so = (GinScanOpaque) palloc(sizeof(GinScanOpaqueData));
	fc_so->keys = NULL;
	fc_so->nkeys = 0;
	fc_so->tempCtx = AllocSetContextCreate(CurrentMemoryContext,
										"Gin scan temporary context",
										ALLOCSET_DEFAULT_SIZES);
	fc_so->keyCtx = AllocSetContextCreate(CurrentMemoryContext,
									   "Gin scan key context",
									   ALLOCSET_DEFAULT_SIZES);
	initGinState(&fc_so->ginstate, fc_scan->indexRelation);

	fc_scan->opaque = fc_so;

	return fc_scan;
}

/*
 * 创建一个新的 GinScanEntry，除非已经存在等效的项，
 * 在这种情况下只需返回它
 */
static GinScanEntry
fc_ginFillScanEntry(GinScanOpaque fc_so, OffsetNumber fc_attnum,
				 StrategyNumber fc_strategy, int32 fc_searchMode,
				 Datum fc_queryKey, GinNullCategory fc_queryCategory,
				 bool fc_isPartialMatch, Pointer fc_extra_data)
{
	GinState   *fc_ginstate = &fc_so->ginstate;
	GinScanEntry fc_scanEntry;
	uint32		fc_i;

	/*
	 * 查找现有的等效条目。
	 *
	 * 带有非空 extra_data 的条目从未被视为相同，因为
	 * 我们无法确切知道 opclass 可能在做什么。
	 */
	if (fc_extra_data == NULL)
	{
		for (fc_i = 0; fc_i < fc_so->totalentries; fc_i++)
		{
			GinScanEntry fc_prevEntry = fc_so->entries[fc_i];

			if (fc_prevEntry->extra_data == NULL &&
				fc_prevEntry->isPartialMatch == fc_isPartialMatch &&
				fc_prevEntry->strategy == fc_strategy &&
				fc_prevEntry->searchMode == fc_searchMode &&
				fc_prevEntry->attnum == fc_attnum &&
				ginCompareEntries(fc_ginstate, fc_attnum,
								  fc_prevEntry->queryKey,
								  fc_prevEntry->queryCategory,
								  fc_queryKey,
								  fc_queryCategory) == 0)
			{
				/* 成功匹配 */
				return fc_prevEntry;
			}
		}
	}

	/* 不，是时候创建一个新条目了 */
	fc_scanEntry = (GinScanEntry) palloc(sizeof(GinScanEntryData));
	fc_scanEntry->queryKey = fc_queryKey;
	fc_scanEntry->queryCategory = fc_queryCategory;
	fc_scanEntry->isPartialMatch = fc_isPartialMatch;
	fc_scanEntry->extra_data = fc_extra_data;
	fc_scanEntry->strategy = fc_strategy;
	fc_scanEntry->searchMode = fc_searchMode;
	fc_scanEntry->attnum = fc_attnum;

	fc_scanEntry->buffer = InvalidBuffer;
	ItemPointerSetMin(&fc_scanEntry->curItem);
	fc_scanEntry->matchBitmap = NULL;
	fc_scanEntry->matchIterator = NULL;
	fc_scanEntry->matchResult = NULL;
	fc_scanEntry->list = NULL;
	fc_scanEntry->nlist = 0;
	fc_scanEntry->offset = InvalidOffsetNumber;
	fc_scanEntry->isFinished = false;
	fc_scanEntry->reduceResult = false;

	/* 将其添加到 so 的数组中 */
	if (fc_so->totalentries >= fc_so->allocentries)
	{
		fc_so->allocentries *= 2;
		fc_so->entries = (GinScanEntry *)
			repalloc(fc_so->entries, fc_so->allocentries * sizeof(GinScanEntry));
	}
	fc_so->entries[fc_so->totalentries++] = fc_scanEntry;

	return fc_scanEntry;
}

/*
 * 将给定类别的隐藏扫描条目附加到扫描键中。
 *
 * 注意：这最好每个扫描键最多只调用一次，因为
 * ginFillScanKey 只留有一个隐藏条目的空间。目前，
 * 似乎足够明确，这是真的，所以我们不再处理
 * 任何交叉检查逻辑。
 */
static void fc_ginScanKeyAddHiddenEntry(GinScanOpaque fc_so, GinScanKey fc_key,
						 GinNullCategory fc_queryCategory)
{
	int			fc_i = fc_key->nentries++;

	/* 策略无关紧要，因为这不是部分匹配项 */
	fc_key->scanEntry[fc_i] = fc_ginFillScanEntry(fc_so, fc_key->attnum,
										 InvalidStrategy, fc_key->searchMode,
										 (Datum) 0, fc_queryCategory,
										 false, NULL);
}

/*
 * 利用 extractQueryFn 的输出初始化下一个 GinScanKey
 */
static void fc_ginFillScanKey(GinScanOpaque fc_so, OffsetNumber fc_attnum,
			   StrategyNumber fc_strategy, int32 fc_searchMode,
			   Datum fc_query, uint32 fc_nQueryValues,
			   Datum *fc_queryValues, GinNullCategory *fc_queryCategories,
			   bool *fc_partial_matches, Pointer *fc_extra_data)
{
	GinScanKey	fc_key = &(fc_so->keys[fc_so->nkeys++]);
	GinState   *fc_ginstate = &fc_so->ginstate;
	uint32		fc_i;

	fc_key->nentries = fc_nQueryValues;
	fc_key->nuserentries = fc_nQueryValues;

	/* 为可能的“隐藏”条目分配一个额外的数组槽位 */
	fc_key->scanEntry = (GinScanEntry *) palloc(sizeof(GinScanEntry) *
											 (fc_nQueryValues + 1));
	fc_key->entryRes = (GinTernaryValue *) palloc0(sizeof(GinTernaryValue) *
												(fc_nQueryValues + 1));

	fc_key->query = fc_query;
	fc_key->queryValues = fc_queryValues;
	fc_key->queryCategories = fc_queryCategories;
	fc_key->extra_data = fc_extra_data;
	fc_key->strategy = fc_strategy;
	fc_key->searchMode = fc_searchMode;
	fc_key->attnum = fc_attnum;

	/*
	 * 最初，GIN_SEARCH_MODE_ALL 模式的扫描键被标记为
	 * excludeOnly。这可能会在后面更改。
	 */
	fc_key->excludeOnly = (fc_searchMode == GIN_SEARCH_MODE_ALL);

	ItemPointerSetMin(&fc_key->curItem);
	fc_key->curItemMatches = false;
	fc_key->recheckCurItem = false;
	fc_key->isFinished = false;
	fc_key->nrequired = 0;
	fc_key->nadditional = 0;
	fc_key->requiredEntries = NULL;
	fc_key->additionalEntries = NULL;

	ginInitConsistentFunction(fc_ginstate, fc_key);

	/* 使用 extractQueryFn 的输出设置正常扫描条目 */
	for (fc_i = 0; fc_i < fc_nQueryValues; fc_i++)
	{
		Datum		fc_queryKey;
		GinNullCategory fc_queryCategory;
		bool		fc_isPartialMatch;
		Pointer		fc_this_extra;

		fc_queryKey = fc_queryValues[fc_i];
		fc_queryCategory = fc_queryCategories[fc_i];
		fc_isPartialMatch =
			(fc_ginstate->canPartialMatch[fc_attnum - 1] && fc_partial_matches)
			? fc_partial_matches[fc_i] : false;
		fc_this_extra = (fc_extra_data) ? fc_extra_data[fc_i] : NULL;

		fc_key->scanEntry[fc_i] = fc_ginFillScanEntry(fc_so, fc_attnum,
											 fc_strategy, fc_searchMode,
											 fc_queryKey, fc_queryCategory,
											 fc_isPartialMatch, fc_this_extra);
	}

	/*
	 * 对于 GIN_SEARCH_MODE_INCLUDE_EMPTY 和 GIN_SEARCH_MODE_EVERYTHING 搜索
	 * 模式，我们立即添加“隐藏”条目。GIN_SEARCH_MODE_ALL 则
	 * 在后面处理，因为我们可能能够省略其隐藏条目。
	 */
	if (fc_searchMode == GIN_SEARCH_MODE_INCLUDE_EMPTY)
		fc_ginScanKeyAddHiddenEntry(fc_so, fc_key, GIN_CAT_EMPTY_ITEM);
	else if (fc_searchMode == GIN_SEARCH_MODE_EVERYTHING)
		fc_ginScanKeyAddHiddenEntry(fc_so, fc_key, GIN_CAT_EMPTY_QUERY);
}

/*
 * 如果有当前扫描键，则释放它们。
 */
void ginFreeScanKeys(GinScanOpaque fc_so)
{
	uint32		fc_i;

	if (fc_so->keys == NULL)
		return;

	for (fc_i = 0; fc_i < fc_so->totalentries; fc_i++)
	{
		GinScanEntry fc_entry = fc_so->entries[fc_i];

		if (fc_entry->buffer != InvalidBuffer)
			ReleaseBuffer(fc_entry->buffer);
		if (fc_entry->list)
			pfree(fc_entry->list);
		if (fc_entry->matchIterator)
			tbm_end_iterate(fc_entry->matchIterator);
		if (fc_entry->matchBitmap)
			tbm_free(fc_entry->matchBitmap);
	}

	MemoryContextResetAndDeleteChildren(fc_so->keyCtx);

	fc_so->keys = NULL;
	fc_so->nkeys = 0;
	fc_so->entries = NULL;
	fc_so->totalentries = 0;
}

void ginNewScanKey(IndexScanDesc fc_scan)
{
	ScanKey		fc_scankey = fc_scan->keyData;
	GinScanOpaque fc_so = (GinScanOpaque) fc_scan->opaque;
	int			fc_i;
	bool		fc_hasNullQuery = false;
	bool		fc_attrHasNormalScan[INDEX_MAX_KEYS] = {false};
	MemoryContext fc_oldCtx;

	/*
	 * 在键上下文中分配所有扫描键信息。（如果
	 * extractQuery 泄漏了任何内容，它不会在扫描或
	 * 重新扫描结束之前重置，但这没关系。）
	 */
	fc_oldCtx = MemoryContextSwitchTo(fc_so->keyCtx);

	/* 如果未提供扫描键，分配额外的 EVERYTHING GinScanKey */
	fc_so->keys = (GinScanKey)
		palloc(Max(fc_scan->numberOfKeys, 1) * sizeof(GinScanKeyData));
	fc_so->nkeys = 0;

	/* 初始化可扩展的 GinScanEntry 指针数组 */
	fc_so->totalentries = 0;
	fc_so->allocentries = 32;
	fc_so->entries = (GinScanEntry *)
		palloc(fc_so->allocentries * sizeof(GinScanEntry));

	fc_so->isVoidRes = false;

	for (fc_i = 0; fc_i < fc_scan->numberOfKeys; fc_i++)
	{
		ScanKey		fc_skey = &fc_scankey[fc_i];
		Datum	   *fc_queryValues;
		int32		fc_nQueryValues = 0;
		bool	   *fc_partial_matches = NULL;
		Pointer    *fc_extra_data = NULL;
		bool	   *fc_nullFlags = NULL;
		GinNullCategory *fc_categories;
		int32		fc_searchMode = GIN_SEARCH_MODE_DEFAULT;

		/*
		 * 我们假设可 GIN 索引操作符是严格的，因此空查询
		 * 参数意味着无法满足的查询。
		 */
		if (fc_skey->sk_flags & SK_ISNULL)
		{
			fc_so->isVoidRes = true;
			break;
		}

		/* 可以调用 extractQueryFn */
		fc_queryValues = (Datum *)
			DatumGetPointer(FunctionCall7Coll(&fc_so->ginstate.extractQueryFn[fc_skey->sk_attno - 1],
											  fc_so->ginstate.supportCollation[fc_skey->sk_attno - 1],
											  fc_skey->sk_argument,
											  PointerGetDatum(&fc_nQueryValues),
											  UInt16GetDatum(fc_skey->sk_strategy),
											  PointerGetDatum(&fc_partial_matches),
											  PointerGetDatum(&fc_extra_data),
											  PointerGetDatum(&fc_nullFlags),
											  PointerGetDatum(&fc_searchMode)));

		/*
		 * 如果返回了虚假的 searchMode，视为 GIN_SEARCH_MODE_ALL；特别注意
		 * 我们不允许 extractQueryFn 选择
		 * GIN_SEARCH_MODE_EVERYTHING。
		 */
		if (fc_searchMode < GIN_SEARCH_MODE_DEFAULT ||
			fc_searchMode > GIN_SEARCH_MODE_ALL)
			fc_searchMode = GIN_SEARCH_MODE_ALL;

		/* 非默认模式需要索引具有占位符 */
		if (fc_searchMode != GIN_SEARCH_MODE_DEFAULT)
			fc_hasNullQuery = true;

		/*
		 * 在默认模式下，未提供键意味着无法满足的查询。
		 */
		if (fc_queryValues == NULL || fc_nQueryValues <= 0)
		{
			if (fc_searchMode == GIN_SEARCH_MODE_DEFAULT)
			{
				fc_so->isVoidRes = true;
				break;
			}
			fc_nQueryValues = 0;	/* 确保值合理 */
		}

		/*
		 * 创建 GinNullCategory 表示。如果 extractQueryFn
		 * 没有创建 nullFlags 数组，我们假设一切都是非空的。
		 * 同时，检测是否存在任何空键。
		 */
		fc_categories = (GinNullCategory *) palloc0(fc_nQueryValues * sizeof(GinNullCategory));
		if (fc_nullFlags)
		{
			int32		fc_j;

			for (fc_j = 0; fc_j < fc_nQueryValues; fc_j++)
			{
				if (fc_nullFlags[fc_j])
				{
					fc_categories[fc_j] = GIN_CAT_NULL_KEY;
					fc_hasNullQuery = true;
				}
			}
		}

		fc_ginFillScanKey(fc_so, fc_skey->sk_attno,
					   fc_skey->sk_strategy, fc_searchMode,
					   fc_skey->sk_argument, fc_nQueryValues,
					   fc_queryValues, fc_categories,
					   fc_partial_matches, fc_extra_data);

		/* 记住我们是否有非 excludeOnly 的键 */
		if (fc_searchMode != GIN_SEARCH_MODE_ALL)
			fc_attrHasNormalScan[fc_skey->sk_attno - 1] = true;
	}

	/*
	 * 处理GIN_SEARCH_MODE_ALL扫描键要求我们对扫描键进行第二次遍历。上述我们将每个这样的扫描键标记为excludeOnly。如果相关列还有任何正常（不是excludeOnly）扫描键，那么我们可以这样保留。否则，一个excludeOnly扫描键必须接收一个GIN_CAT_EMPTY_QUERY隐藏条目并被设置为正常（excludeOnly = false）。
	 */
	for (fc_i = 0; fc_i < fc_so->nkeys; fc_i++)
	{
		GinScanKey	fc_key = &fc_so->keys[fc_i];

		if (fc_key->searchMode != GIN_SEARCH_MODE_ALL)
			continue;

		if (!fc_attrHasNormalScan[fc_key->attnum - 1])
		{
			fc_key->excludeOnly = false;
			fc_ginScanKeyAddHiddenEntry(fc_so, fc_key, GIN_CAT_EMPTY_QUERY);
			fc_attrHasNormalScan[fc_key->attnum - 1] = true;
		}
	}

	/*
	 * 如果没有常规扫描键，生成一个EVERYTHING扫描键以驱动全索引扫描。
	 */
	if (fc_so->nkeys == 0 && !fc_so->isVoidRes)
	{
		fc_hasNullQuery = true;
		fc_ginFillScanKey(fc_so, FirstOffsetNumber,
					   InvalidStrategy, GIN_SEARCH_MODE_EVERYTHING,
					   (Datum) 0, 0,
					   NULL, NULL, NULL, NULL);
	}

	/*
	 * 如果索引版本为0，它可能缺少空值和占位符条目，这将使对空值的搜索和全索引扫描不可靠。如果是这样，则抛出错误。
	 */
	if (fc_hasNullQuery && !fc_so->isVoidRes)
	{
		GinStatsData fc_ginStats;

		ginGetStats(fc_scan->indexRelation, &fc_ginStats);
		if (fc_ginStats.ginVersion < 1)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("old GIN indexes do not support whole-index scans nor searches for nulls"),
					 errhint("To fix this, do REINDEX INDEX \"%s\".",
							 RelationGetRelationName(fc_scan->indexRelation))));
	}

	MemoryContextSwitchTo(fc_oldCtx);

	pgstat_count_index_scan(fc_scan->indexRelation);
}

void ginrescan(IndexScanDesc fc_scan, ScanKey fc_scankey, int fc_nscankeys,
		  ScanKey fc_orderbys, int fc_norderbys)
{
	GinScanOpaque fc_so = (GinScanOpaque) fc_scan->opaque;

	ginFreeScanKeys(fc_so);

	if (fc_scankey && fc_scan->numberOfKeys > 0)
	{
		memmove(fc_scan->keyData, fc_scankey,
				fc_scan->numberOfKeys * sizeof(ScanKeyData));
	}
}


void ginendscan(IndexScanDesc fc_scan)
{
	GinScanOpaque fc_so = (GinScanOpaque) fc_scan->opaque;

	ginFreeScanKeys(fc_so);

	MemoryContextDelete(fc_so->tempCtx);
	MemoryContextDelete(fc_so->keyCtx);

	pfree(fc_so);
}
